'use strict'

const http = require('http')
const fs = require('fs')

const fsp = fs.promises

/** -------------------------------解析Body数据------------------------------------------- */

/**
 * Content-Disposition: form-data; name="file"; filename="xxx"
 * Content-Type: text/plain
 * 
 * @param {string} headerData 
 */
function parseBodyHeaders(headerData){
    let hlist = headerData.split('\r\n').filter(p => p.length > 0)

    //解析content-disposition => hlist[0]
    //按照; 切分数组，选出从第二个元素开始的数组
    let csplit = hlist[0].split('; ').slice(1)

    let filename
    let name
    for(let a of csplit) {
        if(a.indexOf('filename') === 0) {
            filename = a.substring(a.indexOf('=')+2, a.length - 1)
        }else {
            name = a.substring(a.indexOf('=')+2, a.length - 1)
        }
    }

    let headers = {}
    let ind = 0
    let k
    for(let h of hlist) {
        ind = h.indexOf(':')
        k = h.substring(0, ind).toLowerCase()
        headers[k] = h.substring(ind+1).trim()
    }

    return {
        filename,
        name,
        headers,
        contentType: headers['content-type'] || ''
    }
}

function parseBody (bodyData, headers) {
    let ctype = headers['content-type']

    let bdy = ctype.substring(ctype.indexOf('=') + 1)

    let crlf_bdy = `\r\n--${bdy}`

    let header_end_index = bodyData.indexOf('\r\n\r\n',
                                     crlf_bdy.length)
    
    let headerData = bodyData.toString('utf8',
                                        crlf_bdy.length,
                                        header_end_index) 

    //解析文件头信息
    let hd = parseBodyHeaders(headerData)
    
    let file = {
        start: header_end_index + 4,
        end: bodyData.indexOf(crlf_bdy, header_end_index),
        ...hd
    }

    file.length = file.end - file.start

    return file

}

//解析文件扩展名
function parseExtName (filename) {
    if (filename.length < 2) {
        return ''
    }

    let nsplit = filename.split('.').filter(p => p.length > 0)
    if (nsplit.length < 2) return ''
    return `.${nsplit[ nsplit.length - 1]}`
}

/** ---------------------------解析Body数据 - END----------------------------------------- */
let html = `
  <!DOCTYPE html>
  <html>
    <head>
      <title>First HTTP</title>
    </head>
    <body>
      你好。
    </body>
  </html>
`
let routerTable = {

    GET: {
        '/': async (ctx) => {
            ctx.response.setHeader('content-type','text/html;charset=utf-8')
            ctx.end(html)
        },
    
        '/test': async (ctx) => {
            let status = parseInt(Math.random() * 200) + 199
            ctx.response.statusCode = status
            ctx.end('test end')
        },

        '/upload': async (ctx) => {
            let stm = fs.createReadStream('./upload.html')
            stm.pipe(ctx.response, {end: false})
            stm.on('end', () => {
                ctx.end()
            })
        },
    },

    POST: {
        '/upload': async (ctx) => {
            let file = parseBody(ctx.rawBody, ctx.headers)
            console.log(file)
            
            if(!ctx.rawBody){
                ctx.end();
                return
            }
                
            let fname = `${Date.now()}-${Math.random()}` + `${parseExtName(file.filename)}`;
            let fh = await fsp.open(fname, 'w+')
            let fret = await fh.write(ctx.rawBody,file.start, file.length, 0)
            ctx.end(`${fret.bytesWritten}`);
        }
    }
}

http.createServer(async(request, response) => {

    let rm = routerTable[ request.method ]

    let usplit = request.url.split('?')

    let pathname = usplit[0]
    let querystring = usplit[1] || ''

    if(!rm || !rm[ pathname ]) {
        response.statusCode = 404
        response.end('page not found')
        return
    }

    // ctx 是 context的缩写，意为请求上下文，就是一个打包了各种数据和方法的对象
    let ctx = {
        method: request.method,
        path: pathname,
        search: querystring, 
        headers: request.headers,
        request: request,
        response: response,
        end: (data, encoding = 'utf8') => {
            response.end(data, encoding)
        },
        write: (data, encoding = 'utf8') => {
            response.write(data, encoding)
        },
        totalLength: 0,
        rawBody: null,
        bufferList: []
    }

    await new Promise((rv, rj) => {
        if('PD'.indexOf(request.method[0]) >= 0) {
            request.on('data', chunk => {
                ctx.bufferList.push(chunk)
                ctx.totalLength += chunk.length
            })
        }else{
            request.on('data', data => {
                response.statusCode = 400
                response.end('bad requset', () => {
                    request.destroy()
                })
            })
        }

        let resolved = false;
        request.on('end', () => {
            resolved = true;
            rv();
        })

        request.on('close', () => {
            (!resolved) && rv()
        })
    })

    if(!response.writable || response.writableEnded) {
        return
    }

    if(ctx.bufferList.length > 0) {
        ctx.rawBody = Buffer.concat(ctx.bufferList, ctx.totalLength)
        ctx.bufferList = []
    }
    rm[ pathname ](ctx)

})
.listen(3456)